// m_public.go
package bmodel

import (
	"crypto/md5"
	"encoding/hex"

	//"fmt"
	"regexp"
	"time"
)

const ERROR_CANNOT_OPEN_DB string = "db error: database cannot open"

var PASSAWAY_FIELD = [3]string{"Id", "Addtime", "Edittime"}

//cmsuser table
const ERROR_CMSUSER_HAS_BEEN_CREATED string = "db error: cmsuser has been created"
const ERROR_CMSUSER_CANNOT_BE_CREATED string = "db error: cmsuser cannot be created"
const ERROR_CMSUSER_NOTFOUND string = "db error: cmsuser: not found"
const ERROR_EMPTY_CMSUSER_USERNAME_PASSWORD string = "para error: cmsuser: username or password is empty"

//block table
const ERROR_BLOCK_ID string = "db error: block id duplicated/not found"
const ERROR_BLOCK_CANNOT_BE_CREATED string = "db error: block cannot be created"
const ERROR_BLOCK_NOT_FOUND string = "db error: block not found"

//article table
const ERROR_ARTICLE_ID string = "db error: article id duplicated/not found"
const ERROR_ARTICLE_CANNOT_BE_CREATED = "db error: article cannot be created"
const ERROR_ARTICLE_NOT_FOUND string = "db error: article not found"

//para: pages
const ERROR_NUMOFPAGE_OUT_OF_RANGE string = "para error: the number of info in one page out of range"
const MAX_NUMBER_OF_INFO_IN_ONE_PAGE int = 100
const MIN_NUMBER_OF_INFO_IN_ONE_PAGE int = 1

//the interface that model developer and view developer should both know!
var BLOCK_CHOOSES_TABLE = map[string]map[string]map[string]string{
	"state": {
		"debug":   {"code": "0", "str": "debug"},
		"release": {"code": "1", "str": "release"},
	},
}
var ARTICLE_CHOOSES_TABLE = map[string]map[string]map[string]string{
	"state": {
		"debug":   {"code": "0", "str": "debug"},
		"release": {"code": "1", "str": "release"},
	},
}

//default root level blockid
const BLOCK_ROOT_ID int = 0

var BLOCK_ROOT_MAP = map[string]string{
	"Id":    "0",
	"Title": "默认根板块",
}

const BLOCK_NOT_TOP_LEVEL string = "this block isn't a top level block or root block"
const BLOCK_NOT_SUB_LEVEL string = "this block isn't a sub level block"

//do not try to insert addtime/edittime explicitly of all the tables! using gorm!, they should be readonly for golang HTTP server!

//table name will be automatically converted to lower case and added 's' in the end, the result is cmsusers
type Cmsuser struct {
	Id       int    `gorm:"column:id; type:int(11);primary_key;"`
	Username string `gorm:"column:username; type:varchar(32);"`
	Password string `gorm:"column:password; type:varchar(512);"`
}

//table name will be automatically converted to lower case and added 's' in the end, the result is blocks
type Block struct {
	Id       int       `gorm:"column:id; type:int(11);primary_key;"`
	Parentid int       `gorm:"column:parentid; type:int(11); default:0;"`
	Title    string    `gorm:"column:title; type:varchar(70); default:NULL;"`
	Describe string    `gorm:"column:describe; type:text;"`
	State    int       `gorm:"column:state; type:int(11); default:0;"`
	Reserve  string    `gorm:"column:reserve; type:varchar(10000); default:NULL;"`
	Addtime  time.Time `gorm:"column:addtime; default:current_time;"`
	Edittime time.Time `gorm:"column:edittime; default:current_time on update current_time;"`
}

//table name will be automatically converted to lower case and added 's' in the end, the result is articles
type Article struct {
	Id       int       `gorm:"column:id; type:int(11);primary_key;"`
	Blockid  int       `gorm:"column:blockid; type:int(11);"`
	Priority int       `gorm:"column:priority; type:int(11);"`
	Title    string    `gorm:"column:title; type:varchar(70);"`
	Keywords string    `gorm:"column:keywords; type:text;"`
	Brief    string    `gorm:"column:brief; type:varchar(500);"`
	Content  string    `gorm:"column:content; type:text;"`
	Type     int       `gorm:"column:type; type:int(11);"`
	Remark   string    `gorm:"column:remark; type:varchar(128);"`
	State    int       `gorm:"column:state; type:int(11); default:0;"`
	Reserve  string    `gorm:"column:reserve; type:varchar(10000);"`
	Addtime  time.Time `gorm:"column:addtime; default:current_time;"`
	Edittime time.Time `gorm:"column:edittime; default:current_time on update current_time;"`
}

/*
 * judge whether FULLY match!
 * para: targets string: target, r *regexp.Regexp: expression
 * return bool, if fully match true else false
 */
func Isfullymatch(targets string, r *regexp.Regexp) bool {
	s := r.FindStringSubmatch(targets)
	if (len(s) == 0) || (len(s[0]) != len(targets)) {
		return false
	} else {
		return true
	}
}

/*
 * md5 encryption, one-way encryption
 * para: str string before encode
 * return string after encode
 */
func Md5encode(str string) string {
	h := md5.New()
	h.Write([]byte(str))
	return hex.EncodeToString(h.Sum(nil))
}

/*
 * Generate page infos:
 * para: totalCount, numberOfPages, midpage, perpage int
 * return []int means pageIndexs
 *
 * note: 外部必须确保numberOfPages > 1, 否则结果是空的
 */
func GeneratePages(totalCount, numberOfPages, midpage, perpage int) []int {
	if numberOfPages <= 1 {
		return []int{}
	}
	pageIndexs := make([]int, 0)

	var totalPages int
	if (totalCount % perpage) == 0 {
		totalPages = totalCount / perpage
	} else {
		totalPages = totalCount/perpage + 1
	}

	var leftRem, rightRem int

	if (numberOfPages % 2) == 0 { //总页数偶数: 如果中间页为5, 总页数为4, 按如下分布:4567(普通, 全)
		//如果中间页为1, 总页数为4, 按如下分布1234 (特殊: 左边在边界, 右边在边界, 两边在边界)
		leftRem = numberOfPages/2 - 1
		rightRem = numberOfPages / 2
	} else { //总页数奇数: 如果中间页为5, 总页数为3, 按如下分布:456(普通, 全)
		//如果中间页为1, 总页数为3, 按如下分布123 (特殊: 左边在边界, 右边在边界, 两边在边界)
		leftRem = numberOfPages / 2
		rightRem = numberOfPages / 2
	}

	if midpage > leftRem { //左边够放
		if (totalPages - midpage) >= rightRem { //右边够放
			for i := midpage - leftRem; i <= midpage+rightRem; i++ {
				pageIndexs = append(pageIndexs, i) //注:总页数多则不建议这么写, 每次都要重新分配内存, 不如提前分配好
			}
		} else { //右边不够放
			var startPageIndex int
			if (totalPages - numberOfPages + 1) >= 1 {
				startPageIndex = totalPages - numberOfPages + 1
			} else {
				startPageIndex = 1
			}

			for i := startPageIndex; i <= totalPages; i++ {
				pageIndexs = append(pageIndexs, i) //注:总页数多则不建议这么写, 每次都要重新分配内存, 不如提前分配好
			}
		}
	} else { //左边不够放
		if (totalPages - midpage) >= rightRem { //右边够放
			var endPageIndex int
			if numberOfPages <= totalPages {
				endPageIndex = numberOfPages
			} else {
				endPageIndex = totalPages
			}

			for i := 1; i <= endPageIndex; i++ {
				pageIndexs = append(pageIndexs, i) //注:总页数多则不建议这么写, 每次都要重新分配内存, 不如提前分配好
			}
		} else { //右边不够放
			for i := 1; i <= totalPages; i++ {
				pageIndexs = append(pageIndexs, i) //注:总页数多则不建议这么写, 每次都要重新分配内存, 不如提前分配好
			}
		}
	}
	return pageIndexs
}
